package com.jeffgalper.feather;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;

public class VirtualFileChannel extends FileChannel {
  private List<Byte> mData = new ArrayList<Byte>();
  private int mPosition = 0;

  public VirtualFileChannel() {
  }

  public VirtualFileChannel(byte[] data) {
    for (byte b : data) {
      mData.add(b);
    }
  }

  @Override
  public void force(boolean metaData) throws IOException {
  }

  @Override
  protected void implCloseChannel() throws IOException {
  }

  @Override
  public FileLock lock(long position, long size, boolean shared)
      throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public MappedByteBuffer map(MapMode mode, long position, long size)
      throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public long position() throws IOException {
    return mPosition;
  }

  @Override
  public FileChannel position(long newPosition) throws IOException {
    checkLongWillFit(newPosition);
    mPosition = (int) newPosition;
    return this;
  }

  @Override
  public int read(ByteBuffer dst) throws IOException {
    int amount = Math.min(dst.remaining(), mData.size() - mPosition);
    for (int i = 0; i < amount; i++) {
      dst.put(mData.get(mPosition + i));
    }
    mPosition += amount;
    return amount;
  }

  @Override
  public int read(ByteBuffer dst, long position) throws IOException {
    int pos = mPosition;
    position(position);
    int amount = read(dst);
    position(pos);
    return amount;
  }

  @Override
  public long read(ByteBuffer[] dsts, int offset, int length)
      throws IOException {
    int amount = 0;
    for (int i = offset; i < length; i++) {
      amount += read(dsts[i]);
    }
    return amount;
  }

  @Override
  public long size() throws IOException {
    return mData.size();
  }

  @Override
  public long transferFrom(ReadableByteChannel src, long position, long count)
      throws IOException {
    checkLongWillFit(count);
    ByteBuffer bb = ByteBuffer.allocate((int) count);
    src.read(bb);
    bb.rewind();
    return write(bb, position);
  }

  @Override
  public long transferTo(long position, long count, WritableByteChannel target)
      throws IOException {
    checkLongWillFit(count);
    ByteBuffer bb = ByteBuffer.allocate((int) count);
    read(bb, count);
    bb.rewind();
    return target.write(bb);
  }

  private void checkLongWillFit(long count) {
    if (count > Integer.MAX_VALUE) {
      throw new IllegalArgumentException(getClass().getName()
          + " cannot handle a size over " + Integer.MAX_VALUE);
    }
  }

  @Override
  public FileChannel truncate(long size) throws IOException {
    checkLongWillFit(size);
    mData = mData.subList(0, (int) size);
    return this;
  }

  @Override
  public FileLock tryLock(long position, long size, boolean shared)
      throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public int write(ByteBuffer src) throws IOException {
    int amount = src.remaining() - src.position();
    for (int i = src.position(); src.remaining() > 0; i++) {
      mData.add(src.get());
    }
    return amount;
  }

  @Override
  public int write(ByteBuffer src, long position) throws IOException {
    checkLongWillFit(position);
    int pos = mPosition;
    position(position);
    int amount = write(src);
    position(pos);
    return amount;
  }

  @Override
  public long write(ByteBuffer[] srcs, int offset, int length)
      throws IOException {
    int amount = 0;
    for (int i = offset; i < length; i++) {
      amount += write(srcs[i]);
    }
    return amount;
  }

  public int write(byte[] data) {
    for (byte b : data) {
      mData.add(b);
    }
    return data.length;
  }
}
